package core;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Observable;

import javax.swing.JOptionPane;
import javax.swing.Timer;

import projectiles.Nuke;
import projectiles.Projectile;
import towers.Tower;
import enemies.Enemy;
import enemies.Infantry;
import enemies.Jeep;
import enemies.Tank;

/**
 * @author onbekend
 * @version 1.1
 * @since 2011-04-11
 */
public class Model extends Observable {

	private ArrayList<Projectile> projectiles = new ArrayList<Projectile>();
	private ArrayList<Enemy> enemies = new ArrayList<Enemy>();
	private ArrayList<Tower> towers = new ArrayList<Tower>();
	private ArrayList<Explosion> explosions = new ArrayList<Explosion>();

	private Map map;
	private Statistics stats;
	private Timer timer;

	private String firstWave = "Infantry";
	private String secondWave = "Jeep";

	// Nodig om te kijken of er projectielen etc buiten het scherm vallen
	public static final int battlefieldWidth = 780;
	public static final int battlefieldHeight = 425;

	public final static double timerInterval = 0.02; // 50x per sec

	/**
	 * 
	 * @param map de map
	 */
	/* /// Constructor //////////////////////////////////////// */
	public Model(Map map) {
		timer = new Timer((int) (1000 * timerInterval), new TimerHandler());
		timer.start();

		this.stats = new Statistics(this);
		this.map = map;
	}

	/**
	 * @param level
	 * @return aantal enemies dat er komen voor dat level
	 */
	/* /// getCountEnemies //////////////////////////////////////// */
	public int getCountEnemies(int level) {
		return (int) (level / 3.0 + 1);
	}

	/**
	 * Volgende wave van enemies
	 */
	/* /// nextWave //////////////////////////////////////// */
	public void nextWave() {
		if (map.getBase().getHealth() > 0 && !isPaused()) {
			stats.updateNextWave();

			// Level verhogen
			this.stats.setLevel(this.stats.getLevel() + 1);

			// Eerste punt bepalen
			int random = (int) (Math.random() * map.getRoutes().size());
			ArrayList<Point2D.Double> randomRoute = map.getRoutes().get(random);

			Point2D.Double pos = randomRoute.get(0);
			int x = 0;

			// Nieuwe enemies toevoegen aan arraylist
			for (int i = 0; i < getCountEnemies(stats.getLevel()); i++) {
				// HARDCODED: health, speed
				// Tanks en jeep zijn natuurlijk iets sterker dan infantry
				x -= 35;
				if (firstWave.equals("Tank")) {
					x -= 15;
					enemies.add(new Tank(randomRoute, new Point2D.Double(pos.x + x, pos.y), stats.getLevel() * 17,
							(int) Math.sqrt(stats.getLevel()) * 15 + 50));
				}
				if (firstWave.equals("Jeep")) {
					x -= 5;
					enemies.add(new Jeep(randomRoute, new Point2D.Double(pos.x + x, pos.y), stats.getLevel() * 16,
							(int) Math.sqrt(stats.getLevel()) * 15 + 50));
				}
				if (firstWave.equals("Infantry")) {
					enemies.add(new Infantry(randomRoute, new Point2D.Double(pos.x + x, pos.y), stats.getLevel() * 15,
							(int) Math.sqrt(stats.getLevel()) * 15 + 50));
				}
			}

			// first en second bepalen
			int rand1 = (int) (Math.random() * 3);
			firstWave = secondWave;
			if (rand1 == 0) {
				secondWave = "Tank";
			}
			if (rand1 == 1) {
				secondWave = "Jeep";
			}
			if (rand1 == 2) {
				secondWave = "Infantry";
			}

			setChanged();
			notifyObservers("nextwave");

			setChanged();
			notifyObservers("update");
		}
	}

	/**
	 * @param tower de toren die gekocht moet worden
	 * @param p het punt waar de toren moet komen te staan
	 * @return true als de toren is geplaatst anders return false
	 */
	/* /// buyTowers //////////////////////////////////////// */
	public boolean buyTower(Tower tower, Point2D.Double p) {
		if (map.getBase().getHealth() > 0 && stats.getMoney() >= tower.getCost()) {

			stats.setMoney(stats.getMoney() - tower.getCost());
			tower.setPosition(p);
			tower.calcBounds();
			towers.add(tower);

			// Laat View weten dat we moeten repainten
			setChanged();
			notifyObservers("repaint");

			return true;
		}
		return false;
	}

	/* /// getMap //////////////////////////////////////// */
	public Map getMap() {
		return map;
	}

	/* /// getEnemies //////////////////////////////////////// */
	public ArrayList<Enemy> getEnemies() {
		return enemies;
	}

	/* /// setEnemies //////////////////////////////////////// */
	public void setEnemies(ArrayList<Enemy> enemies) {
		this.enemies = enemies;
	}

	/* /// getProjectiles //////////////////////////////////////// */
	public ArrayList<Projectile> getProjectiles() {
		return projectiles;
	}

	/* /// setProjectiles //////////////////////////////////////// */
	public void setProjectiles(ArrayList<Projectile> projectiles) {
		this.projectiles = projectiles;
	}

	/* /// getTowers //////////////////////////////////////// */
	public ArrayList<Tower> getTowers() {
		return towers;
	}

	/* /// getExplosions //////////////////////////////////////// */
	public ArrayList<Explosion> getExplosions() {
		return explosions;
	}

	/* /// setTowers //////////////////////////////////////// */
	public void setTowers(ArrayList<Tower> towers) {
		this.towers = towers;
	}

	/* /// getStats //////////////////////////////////////// */
	public Statistics getStats() {
		return stats;
	}

	/* /// togglePause //////////////////////////////////////// */
	public void togglePause() {
		if (timer.isRunning()) {
			timer.stop();

			stats.paused();
		} else {
			timer.start();

			stats.unpaused();
		}

		setChanged();
		notifyObservers("repaint");
	}

	/* /// isPaused //////////////////////////////////////// */
	public boolean isPaused() {
		return !timer.isRunning();
	}

	/**
	 * @param t De toren die gerepareerd dient te worden
	 * @return true als het gelukt is om te repareren, anders false.
	 */
	/* /// repair //////////////////////////////////////// */
	public boolean repair(Tower t) {
		if (t != null && t.getHealth() > 0 && t.getHealth() < t.getMaxHealth() && t.getPosition() != null) {
			// De kosten zijn de originele kosten maal health percentage en 10%
			// minder omdat het goedkoper moet zijn dan nieuw bouwen
			int cost = (int) (t.getCost() * (1 - t.getHealthPercentage()) * 0.9);

			if (stats.getMoney() >= cost) {
				t.setHealth(t.getMaxHealth());
				stats.reduceMoney(cost);

				System.out.println("Toren gerepareerd");

				// Repaint de health die erbij staat
				setChanged();
				notifyObservers("repaint");
				return true;
			}
		}
		return false;
	}

	/**
	 * @param t De toren die verkocht dient te worden
	 * @return true als het gelukt is om te verkopen, anders false
	 */
	/* /// sell //////////////////////////////////////// */
	public boolean sell(Tower t) {
		if (t != null && t.getHealth() > 0 && t.getPosition() != null) {
			towers.remove(t);

			// De opbrengsten zijn 75% van de originele kosten, maal health
			// percentage
			int profit = (int) (0.75 * t.getCost() * t.getHealthPercentage());
			stats.increaseMoney(profit);

			t.setHealth(0);

			// Tower doet boom!
			explosions.add(new Explosion(t.getMiddle(), 50));

			System.out.println("Toren verkocht");

			setChanged();
			notifyObservers("repaint");

			return true;
		}
		return false;
	}

	/* /// repaint //////////////////////////////////////// */
	public void repaint() {
		setChanged();
		notifyObservers("repaint");
	}
	
	/* /// TimerHandler //////////////////////////////////////// */
	class TimerHandler implements ActionListener {
		public void actionPerformed(ActionEvent e) {

			// Moeten we repainten?
			boolean repaint = false;

			// Spel is afgelopen als basis kapot is
			// We hoeven niets meer up te daten of te redrawen, alles behalve de
			// explosies

			// Als health van basis 0 is, dan doe explosie
			if (map.getBase().getHealth() == 0) {
				System.out.println("Basis is kapot");

				explosions.add(new Explosion(map.getBase().getMiddle(), 175, true));
				repaint = true;
				map.getBase().setHealth(-1);

				showInputName();
				stats.insertStats();
				showHighScore();
			} else if (map.getBase().getHealth() > 0) {

				if (!isPaused()) {
					// Stats updaten als spel niet gepauzeerd is
					stats.update();

					setChanged();
					notifyObservers("stats");

					// next wave
					if (stats.getNextWave() <= 0)
						nextWave();
				}

				// Bijhouden welke units weg mogen
				ArrayList<Enemy> deadEnemies = new ArrayList<Enemy>();
				ArrayList<Projectile> deadProjectiles = new ArrayList<Projectile>();
				ArrayList<Tower> deadTowers = new ArrayList<Tower>();

				for (Tower t : towers) {
					if (t.update())
						repaint = true;
				}

				for (Projectile p : projectiles) {
					if (p.update())
						repaint = true;

					if (p.isOutsideView())
						deadProjectiles.add(p);
				}

				for (Enemy en : enemies) {
					if (en.update())
						repaint = true;
				}

				// Torens in de basis verwijderen
				for (Enemy en : enemies) {
					if (en.getHealth() > 0
							&& en.bounds.intersects(map.getBase().position.x, map.getBase().position.y, map.getBase()
									.getSize().getWidth(), map.getBase().getSize().getHeight())) {

						// De enemy kan niets meer doen
						en.setHealth(0);

						// Enemy verwijderen
						deadEnemies.add(en);

						// Base health verminderen
						map.getBase().setHealth(Math.max(map.getBase().getHealth() - 1, 0));
					}
				}

				// Torens laten schieten
				for (Tower t : towers) {
					for (Enemy en : enemies) {
						if (!en.isOutsideView() && en.getHealth() > 0 && en.position.distance(t.position) <= t.range) {
							t.setAimAtEnemy(en);

							if (t.canShoot()) {
								projectiles.add(t.shoot());
								repaint = true;
							}
						}
					}
				}

				// Vijanden laten schieten
				for (Enemy en : enemies) {
					for (Tower t : towers) {
						if (en.position.distance(t.position) <= en.range) {
							en.setTower(t);

							if (en.canShoot()) {
								projectiles.add(en.shoot());
								repaint = true;
							}
						}
					}
				}

				// Projectile kan wat raken
				ArrayList<ShootingUnit> shootingUnits = new ArrayList<ShootingUnit>();
				shootingUnits.addAll(towers);
				shootingUnits.addAll(enemies);

				for (Projectile t : projectiles) {
					// Nuke controleren
					if (t instanceof Nuke) {
						Nuke n = (Nuke) t;

						// Nen nieuwe enemy toewijzen als nuke geen (levende)
						// vijand heeft
						if (n == null || n.getEnemy().getHealth() <= 0) {
							n.calcEnemyForNuke(enemies);
						}

					}

					for (ShootingUnit en : shootingUnits) {
						if (t.bounds.intersects(en.getPosition().x, en.getPosition().y, en.getSize().getWidth(), en
								.getSize().getHeight())) {
							// Ze mogen niet op hetzelfde type schieten
							if ((t.getOrigin() instanceof Tower && en instanceof Enemy)
									|| (t.getOrigin() instanceof Enemy && en instanceof Tower)) {
								int health = Math.max(0, en.getHealth() - t.getDamage());
								if (health == 0) {
									if (en instanceof Enemy) {
										stats.incrementKills();
										// Je krijgt geld erbij als je een enemy
										// afmaakt
										// de wortel van level zodat als level
										// hoger word, je niet teveel krijgt
										// delen door 30 om het flink te
										// verlagen en + 10 zodat je niet te
										// weinig krijgt in de eerste levels
										stats.setMoney((int) (stats.getMoney() + (Math.sqrt(stats.getLevel()) / 30 + 10)));
										deadEnemies.add((Enemy) en);

										// Nuke explodeert
										if (t instanceof Nuke) {
											explosions.add(new Explosion(t.getMiddle(), 40));
										}
									} else if (en instanceof Tower) {
										deadTowers.add((Tower) en);

										// Tower doet boom!
										explosions.add(new Explosion(en.getMiddle(), 50));
									}
								}

								en.setHealth(health);

								// Projectile ook verwijderen
								deadProjectiles.add(t);
							}
						}
					}
				}

				// Verwijder dead units
				for (Enemy en : deadEnemies) {
					repaint = true;
					enemies.remove(en);
				}
				deadEnemies.clear();
				for (Projectile p : deadProjectiles) {
					repaint = true;
					projectiles.remove(p);
				}
				deadProjectiles.clear();
				for (Tower t : deadTowers) {
					repaint = true;
					towers.remove(t);
				}
				deadTowers.clear();

				// Update stats
				setChanged();
				notifyObservers("updateStats");
			}

			// Update explosions
			// Verwijder explosions die klaar zijn
			ArrayList<Explosion> doneExplosions = new ArrayList<Explosion>();
			for (Explosion ex : explosions) {

				if (ex.isDone())
					doneExplosions.add(ex);
			}

			for (Explosion ex : doneExplosions) {
				explosions.remove(ex);
			}
			doneExplosions.clear();

			// Laat view weten dat we graag willen repainten
			if (repaint) {
				setChanged();
				notifyObservers("repaint");
			}

			// Elke timer tik de stats laten updaten
			stats.update();
		}
	}

	/**
	 * @return the firstWave
	 */
	/* /// getFirstWave //////////////////////////////////////// */
	public String getFirstWave() {
		return firstWave;
	}

	/**
	 * @return the secondWave
	 */
	/* /// getSecondWave //////////////////////////////////////// */
	public String getSecondWave() {
		return secondWave;
	}

	/* /// showInputName //////////////////////////////////////// */
	public void showInputName() {
		String name = JOptionPane.showInputDialog("What is your name?");
		String email = JOptionPane.showInputDialog("What is your email?");
		getStats().setName(name);
		getStats().setEmail(email);
	}

	/* /// showHighScore //////////////////////////////////////// */
	public void showHighScore() {
		setChanged();
		notifyObservers("highscore");

	}
}
